// go:build linux && !android

package p2p

import (
	"context"
	"encoding/binary"
	"errors"
	"feishup2pclient/config"
	"feishup2pclient/data"
	"feishup2pclient/msg"

	//"feishup2pclient/tun"
	MSG "feishup2pclient/msg"
	"feishup2pclient/winipcfg"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/sys/windows"
	"golang.zx2c4.com/wintun"
	"golang.zx2c4.com/wireguard/tun"

	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	swarm "github.com/libp2p/go-libp2p-swarm"
	ma "github.com/multiformats/go-multiaddr"

	//"feishup2pclient/msg"
	"net/netip"
	//"golang.org/x/net/icmp"
	//"golang.org/x/net/ipv4"
)

var (
	nodeAddr          string
	nodeName          string
	nodePrivateKey    string
	RevLookup         *sync.Map                 // id:ip
	activeStreams     map[string]network.Stream // ip:stream
	peerTable         *sync.Map                 // ip:id
	ctx               = context.Background()
	prettyDiscoveryCh chan PeerInfo
	relayClientTable  *sync.Map // ip:id
	relayServerId     peer.ID   // id
	relayServerIp     string
	kdht              *dht.IpfsDHT
	Routes            = make(map[string]string)
)

type PeerInfo struct {
	id peer.ID
	ip string
}

func netsh(args ...string) (err error) {
	cmd := exec.Command("netsh", args...)
	err = cmd.Run()
	return
}

func Delete(name string) error {
	return netsh("interface", "delete", "interface", name)
}

var Dev tun.Device

func StartP2P(stopCtx context.Context, name, addr string) {

	nodeAddr = addr
	nodeName = name
	if config.PrvKey == "" {

		// log.Info("No private key provided. Generating a new one...")
		// Creates a new RSA key pair for this host.
		h, err := libp2p.New()
		if err != nil {
			MSG.ErrCheck(fmt.Sprintf("%s", err))
			panic(err)
		}
		//节点私钥
		keyBytes, err := crypto.MarshalPrivateKey(h.Peerstore().PrivKey(h.ID()))
		if err != nil {
			MSG.ErrCheck(fmt.Sprintf("%s", err))
			panic(err)
		}

		config.PrvKey = string(keyBytes)
		config.WriteConfig(config.PrvKey)
		if err := h.Close(); err != nil {
			MSG.ErrCheck(fmt.Sprintf("%s", err))
			panic(err)
		}
		_ = h.Close()

	}
	// nodePrivateKey = string(keyBytes)
	// // log.Infof("节点私钥:%s", string(keyBytes))
	// // 连接信息
	// log.Infof("连接信息：%s/%s", host.ID().String(), nodeAddr)
	// data.SelfP2PID = host.ID().String()
	// data.SelfP2PIP = nodeAddr
	//wintun
	id := &windows.GUID{
		0x0000000,
		0xFFFF,
		0xFFFF,
		[8]byte{0xFF, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
	}
	_ = wintun.Uninstall()
	ifname := "wintun"
	dev, err := tun.CreateTUNWithRequestedGUID(ifname, id, 0)
	if err != nil {
		fmt.Println("创建 TUN 设备失败：", err)
		MSG.ErrCheck(fmt.Sprintf("创建 TUN 设备失败：%s", err))
		panic(err)
	}
	Dev = dev
	defer Dev.Close()
	// 保存原始设备句柄
	nativeTunDevice := dev.(*tun.NativeTun)
	msg.SendMessage(fmt.Sprintf("ParsePrefix- nodeAddr - %s - %s", nodeAddr, nodeAddr+"/24"))
	ip1, err := netip.ParsePrefix(nodeAddr)
	if err != nil {
		MSG.ErrCheck(fmt.Sprintf("%s", err))
		panic(err)
	}

	// 获取LUID用于配置网络
	link := winipcfg.LUID(nativeTunDevice.LUID())

	err = link.SetIPAddresses([]netip.Prefix{ip1})
	if err != nil {
		MSG.ErrCheck(fmt.Sprintf("%s", err))
		panic(err)
	}

	/*
		// Create new TUN device
		tun.TunDev, err = tun.New(
			nodeName,
			tun.Address(nodeAddr),
			tun.MTU(1420),
		)
		if err != nil {
			panic(err)
		}

		err = tun.TunDev.Up()
		if err != nil {
			panic(err)
		}
	*/

	port, err := verifyPort(8001)
	if err != nil {
		MSG.SendMessage(fmt.Sprintf("暂无端口可用: %s", err.Error()))
		return
	}

	// Create libp2p node
	host, kdht, err := CreateNode(
		ctx,
		config.PrvKey,
		port,
		streamHandler,
	)

	if err != nil {
		MSG.ErrCheck(fmt.Sprintf("CreateNode error: ", err.Error()))
		return
	}
	data.LibP2PHost = host
	data.DHT = kdht
	msg.SendMessage(fmt.Sprintf("连接信息：%s/%s", host.ID().String(), nodeAddr))
	MSG.SendMessage(fmt.Sprintf("本地节点peerId:%s", host.ID().String()))
	data.SelfP2PID = host.ID().String()
	data.SelfP2PIP = nodeAddr
	ip, _, err := net.ParseCIDR(addr)
	if err != nil {
		MSG.ErrCheck(fmt.Sprintf("%s", err))
		panic(err)
	}

	go Discover(ctx, host, kdht, peerTable)
	go prettyDiscovery(stopCtx, host, peerTable)
	MSG.SendMessage("[+] Network Setup Complete...Waiting on Node Discovery")
	fmt.Println("[+] Network Setup Complete...Waiting on Node Discovery")

	// + ----------------------------------------+
	// | Listen For New Packets on TUN Interface |
	// + ----------------------------------------+

	// Initialize active streams map and packet byte array.
	activeStreams = make(map[string]network.Stream)
	packet := make([]byte, 1420)
	for {

		// Read in a packet from the tun device.
		/*
			plen, err := tun.TunDev.Iface.Read(packet)

			if err != nil {
				panic(err)
			}
		*/
		select {
		case <-stopCtx.Done():
			fmt.Println("Exiting goroutine")
			return
		default:
			plen, err := dev.Read(packet, 0)
			dstIP := net.IPv4(packet[16], packet[17], packet[18], packet[19])
			// Decode the packet's destination address
			dst := net.IPv4(packet[16], packet[17], packet[18], packet[19]).String()
			// 路由转发
			for route, _ := range Routes {
				_, network, err := net.ParseCIDR(route)
				if err != nil {
					continue
				}
				if network.Contains(dstIP) {
					src := net.IPv4(packet[12], packet[13], packet[14], packet[15])
					if src.Equal(ip) {
						dst = Routes[route]
					}
				}
			}
			// Check if we already have an open connection to the destination peer.
			stream, ok := activeStreams[dst]
			if ok {
				// Write out the packet's length to the libp2p stream to ensure
				// we know the full size of the packet at the other end.
				err = binary.Write(stream, binary.LittleEndian, uint16(plen))
				if err == nil {
					// Write the packet out to the libp2p stream.
					// If everyting succeeds continue on to the next packet.
					_, err = stream.Write(packet[:plen])
					if err == nil {
						continue
					}
				}
				// If we encounter an error when writing to a stream we should
				// close that stream and delete it from the active stream map.
				stream.Close()
				delete(activeStreams, dst)
			}

			// Check if the destination of the packet is a known peer to
			// the interface.
			if peerId, ok := peerTable.Load(dst); ok {
				peer, ok := peerId.(peer.ID)
				if ok {
					// stream, err = host.NewStream(network.WithUseTransient(ctx, Protocol), peer, Protocol)
					stream, err := host.NewStream(ctx, peer, Protocol)
					if err != nil {
						if strings.HasPrefix(err.Error(), "transient connection to peer") {
							stream, err = host.NewStream(network.WithUseTransient(ctx, Protocol), peer, Protocol)
							if err != nil {
								continue
							}
						}
						continue
					}
					// Write packet length
					err = binary.Write(stream, binary.LittleEndian, uint16(plen))
					if err != nil {
						stream.Close()
						continue
					}
					// Write the packet
					_, err = stream.Write(packet[:plen])
					if err != nil {
						stream.Close()
						continue
					}

					// If all succeeds when writing the packet to the stream
					// we should reuse this stream by adding it active streams map.
					activeStreams[dst] = stream
				}

			}
		}
	}
}
func Up(id, ip string) error {
	RevLookup.Store(id, ip)
	peerId, err := peer.Decode(id)
	if err != nil {
		return err
	}
	peerTable.Store(ip, peerId)
	prettyDiscoveryCh <- PeerInfo{
		id: peerId,
		ip: ip,
	}

	return nil
}

func Down(id, ip string) {
	peerId, err := peer.Decode(id)
	if err != nil {
		msg.ErrCheck(err.Error())
	}
	RevLookup.Delete(id)
	peerTable.Delete(ip)
	data.LibP2PHost.Network().(*swarm.Swarm).Backoff().Clear(peerId)
}

func prettyDiscovery(ctx context.Context, node host.Host, peerTable *sync.Map) {
	// 建立一个临时对等映射，将查询限制为仅限于未连接的对等。

	for {
		select {
		case <-ctx.Done():
			return
		case peer, ok := <-prettyDiscoveryCh:
			if ok {
				// stream, err := node.NewStream(network.WithUseTransient(ctx, Protocol), peer.id, Protocol)
				stream, err := node.NewStream(ctx, peer.id, Protocol)
				if err != nil {
					if strings.HasPrefix(err.Error(), "failed to dial") ||
						strings.HasPrefix(err.Error(), "no addresses") {
						// Attempt to connect to peers slowly when they aren't found.
						time.Sleep(5 * time.Second)

						// resend to discovery channel
						prettyDiscoveryCh <- peer
						continue
					}
					if strings.HasPrefix(err.Error(), "transient connection to peer") {
						stream, err := node.NewStream(network.WithUseTransient(ctx, Protocol), peer.id, Protocol)
						if err != nil {
							// Attempt to connect to peers slowly when they aren't found.
							time.Sleep(5 * time.Second)

							// resend to discovery channel
							prettyDiscoveryCh <- peer
							continue
						} else {
							fmt.Printf("[+] Connection to %s Successful. Network Ready.\n", peer.ip)
							stream.Close()
						}
					}

				}
				if err == nil {
					fmt.Printf("[+] Connection to %s Successful. Network Ready.\n", peer.ip)
					stream.Close()
				}
			}
		}
	}

}

func verifyPort(port int) (int, error) {
	var ln net.Listener
	var err error

	// If a user manually sets a port don't try to automatically
	// find an open port.
	if port != 8001 {
		ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
		if err != nil {
			return port, errors.New("could not create node, listen port already in use by something else")
		}
	} else {
		// Automatically look for an open port when a custom port isn't
		// selected by a user.
		for {
			ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
			if err == nil {
				break
			}
			if port >= 65535 {
				return port, errors.New("failed to find open port")
			}
			port++
		}
	}
	if ln != nil {
		ln.Close()
	}
	return port, nil
}

func streamHandler(stream network.Stream) {
	// MSG.SendMessage(fmt.Sprintf("remote:" + stream.Conn().RemotePeer().String()))
	// If the remote node ID isn't in the list of known nodes don't respond.
	if _, ok := RevLookup.Load(stream.Conn().RemotePeer().String()); !ok {
		stream.Reset()
		return
	}
	var packet = make([]byte, 1420)
	var packetSize = make([]byte, 2)
	for {
		// Read the incoming packet's size as a binary value.
		_, err := stream.Read(packetSize)
		if err != nil {
			stream.Close()
			return
		}

		// Decode the incoming packet's size from binary.
		size := binary.LittleEndian.Uint16(packetSize)

		// Read in the packet until completion.
		var plen uint16 = 0
		for plen < size {

			if size > 1420 {
				tmp, err := stream.Read(packet[plen:1420])
				plen += uint16(tmp)
				if err != nil {
					stream.Close()
					return
				}
			} else {
				tmp, err := stream.Read(packet[plen:size])
				plen += uint16(tmp)
				if err != nil {
					stream.Close()
					return
				}
			}

		}

		//_, err = tun.TunDev.Iface.Write(packet[:size])
		_, err = Dev.Write(packet, 0)
		if err != nil {
			msg.ErrCheck(err.Error())
		}
	}
}

func SetRelayServer(id, addr string) error {
	peerId, err := peer.Decode(id)
	if err != nil {
		relayServerId = ""
		return err
	}
	relayServerId = peerId
	relayServerIp = addr
	return nil
}

func isRelayAddress(a ma.Multiaddr) bool {
	_, err := a.ValueForProtocol(ma.P_CIRCUIT)
	return err == nil
}

func containsRelayAddr(addrs []ma.Multiaddr) bool {
	for _, addr := range addrs {
		if isRelayAddress(addr) {
			return true
		}
	}
	return false
}

func CheckConnectType(id peer.ID) string {
	_, err := data.DHT.FindPeer(ctx, id)
	if err != nil {
		return "relay"
	}
	return "p2p"
}

func init() {
	RevLookup = &sync.Map{}
	peerTable = &sync.Map{}
	prettyDiscoveryCh = make(chan PeerInfo, 256)
	relayClientTable = &sync.Map{}
}
